﻿using System;
using System.Buffers;
using System.Runtime.CompilerServices;

using Unity.String.Text.Charset;

namespace Unity.String.Text
{
    internal static partial class Utf16Formatter
    {
        const char separator = ' ';

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static void FormatTo<TBufferWriter, T>(ref TBufferWriter stringBuilder, T arg, int width, ReadOnlySpan<char> format, string argName)
            where TBufferWriter : IBufferWriter<char>
        {
            if (width <= 0) // leftJustify
            {
                var span = stringBuilder.GetSpan(0);
                if (!Utf16ValueStringBuilder.FormatterCache<T>.TryFormatDelegate(arg, span, out var argWritten, format))
                {
                    stringBuilder.Advance(0);
                    span = stringBuilder.GetSpan(Math.Max(span.Length + 1, argWritten));
                    if (!Utf16ValueStringBuilder.FormatterCache<T>.TryFormatDelegate(arg, span, out argWritten, format))
                    {
                        StringFormatterException.ThrowArgumentException(argName);
                    }
                }
                stringBuilder.Advance(argWritten);

                width *= -1;
                int padding = width - argWritten;
                if (width > 0 && padding > 0)
                {
                    var paddingSpan = stringBuilder.GetSpan(padding);
                    paddingSpan.Fill(separator);
                    stringBuilder.Advance(padding);
                }
            }
            else
            {
                FormatToRightJustify(ref stringBuilder, arg, width, format, argName);
            }
        }

        private static void FormatToRightJustify<TBufferWriter, T>(ref TBufferWriter stringBuilder, T arg, int width, ReadOnlySpan<char> format, string argName)
            where TBufferWriter : IBufferWriter<char>
        {
            if (typeof(T) == typeof(string))
            {
                var source = Unsafe.As<string>(arg);
                int padding = width - source.Length;
                if (padding > 0)
                {
                    var paddingSpan = stringBuilder.GetSpan(padding);
                    paddingSpan.Fill(separator);
                    stringBuilder.Advance(padding);
                }

                var span = stringBuilder.GetSpan(source.Length);
                source.AsSpan().CopyTo(span);
                stringBuilder.Advance(source.Length);
            }
            else
            {
                Span<char> source = stackalloc char[typeof(T).IsValueType ? Unsafe.SizeOf<T>() * 8 : 1024];

                if (!Utf16ValueStringBuilder.FormatterCache<T>.TryFormatDelegate(arg, source, out var charsWritten, format))
                {
                    source = stackalloc char[source.Length * 2];
                    if (!Utf16ValueStringBuilder.FormatterCache<T>.TryFormatDelegate(arg, source, out charsWritten, format))
                    {
                        StringFormatterException.ThrowArgumentException(argName);
                    }
                }

                int padding = width - charsWritten;
                if (padding > 0)
                {
                    var paddingSpan = stringBuilder.GetSpan(padding);
                    paddingSpan.Fill(separator);
                    stringBuilder.Advance(padding);
                }

                var span = stringBuilder.GetSpan(charsWritten);
                source.CopyTo(span);
                stringBuilder.Advance(charsWritten);
            }
        }
    }

    internal static partial class Utf8FormatHelper
    {
        const byte sp = (byte)' ';

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static void FormatTo<TBufferWriter, T>(ref TBufferWriter stringBuilder, T arg, int width, StandardFormat format, string argName)
            where TBufferWriter : IBufferWriter<byte>
        {
            if (width <= 0) // leftJustify
            {
                var span = stringBuilder.GetSpan(0);
                if (!Utf8ValueStringBuilder.FormatterCache<T>.TryFormatDelegate(arg, span, out var argWritten, format))
                {
                    stringBuilder.Advance(0);
                    span = stringBuilder.GetSpan(Math.Max(span.Length + 1, argWritten));
                    if (!Utf8ValueStringBuilder.FormatterCache<T>.TryFormatDelegate(arg, span, out argWritten, format))
                    {
                        StringFormatterException.ThrowArgumentException(argName);
                    }
                }
                stringBuilder.Advance(argWritten);

                width *= -1;
                int padding = width - argWritten;
                if (width > 0 && padding > 0)
                {
                    var paddingSpan = stringBuilder.GetSpan(padding);
                    paddingSpan.Fill(sp);
                    stringBuilder.Advance(padding);
                }
            }
            else
            {
                FormatToRightJustify(ref stringBuilder, arg, width, format, argName);
            }
        }

        private static void FormatToRightJustify<TBufferWriter, T>(ref TBufferWriter stringBuilder, T arg, int width, StandardFormat format, string argName)
            where TBufferWriter : IBufferWriter<byte>
        {
            if (typeof(T) == typeof(string))
            {
                var source = Unsafe.As<string>(arg);
                int padding = width - source.Length;
                if (padding > 0)
                {
                    var paddingSpan = stringBuilder.GetSpan(padding);
                    paddingSpan.Fill(sp);
                    stringBuilder.Advance(padding);
                }

                UnityString.AppendChars(ref stringBuilder, source.AsSpan());
            }
            else
            {
                Span<byte> source = stackalloc byte[typeof(T).IsValueType ? Unsafe.SizeOf<T>() * 8 : 1024];

                if (!Utf8ValueStringBuilder.FormatterCache<T>.TryFormatDelegate(arg, source, out var charsWritten, format))
                {
                    source = stackalloc byte[source.Length * 2];
                    if (!Utf8ValueStringBuilder.FormatterCache<T>.TryFormatDelegate(arg, source, out charsWritten, format))
                    {
                        StringFormatterException.ThrowArgumentException(argName);
                    }
                }

                int padding = width - charsWritten;
                if (padding > 0)
                {
                    var paddingSpan = stringBuilder.GetSpan(padding);
                    paddingSpan.Fill(sp);
                    stringBuilder.Advance(padding);
                }

                var span = stringBuilder.GetSpan(charsWritten);
                source.CopyTo(span);
                stringBuilder.Advance(charsWritten);
            }
        }
    }
}
